package rpc

import (
	"context"
	pool "github.com/jolestar/go-commons-pool/v2"
	"log"
	"reflect"
	"time"
)

// PooledClient 声明客户端
type PooledClient struct {
	serverAddr  string
	services    map[string]interface{}
	sessionPool *pool.ObjectPool
	ctx         context.Context
}

// NewPooledClient 创建客户端对象
func NewPooledClient(serverAddr string, coreSize, maxSize int) *PooledClient {
	ctx := context.Background()
	//config := pool.NewDefaultPoolConfig()
	//sessionPool := pool.NewObjectPool(ctx, &MyPooledObjFactory{
	//	serverAddr: serverAddr,
	//}, config)
	config := pool.ObjectPoolConfig{
		MaxTotal:           maxSize,
		MaxIdle:            coreSize,
		BlockWhenExhausted: true,
	}
	sessionPool := pool.NewObjectPool(ctx, &MyPooledObjFactory{
		serverAddr: serverAddr,
	}, &config)

	return &PooledClient{
		serverAddr:  serverAddr,
		services:    make(map[string]interface{}),
		sessionPool: sessionPool,
		ctx:         ctx,
	}
}

// ConfigRemoteMethod 实现通用池化的RPC客户端: 绑定RPC使用的方法, 传入访问的函数名// ConfigRemoteMethod 配置远程方法及提供者信息
// methodId:		远程方法Id
// fooPtr:			远程方法声明
func (c *PooledClient) ConfigRemoteMethod(methodId string, fooPtr interface{}) {
	if _, ok := c.services[methodId]; !ok {
		// not config
		c.services[methodId] = fooPtr
	}
	c.CallPooledRPC(methodId, fooPtr)
	log.Printf("pooled client config methodId:%s -> %T, provider:%s success", methodId, fooPtr, c.serverAddr)
}

// CallPooledRPC 函数具体实现在Server端, Client只有函数原型; 使用MakeFunc() 完成原型到函数的调用; fPtr指向函数原型
func (c *PooledClient) CallPooledRPC(rpcName string, fPtr interface{}) {
	// 通过反射，获取fPtr未初始化的函数原型
	fn := reflect.ValueOf(fPtr).Elem()
	// 另一个函数，是对第一个函数参数操作
	f := func(args []reflect.Value) []reflect.Value {
		// 处理输入的参数
		inArgs := make([]interface{}, 0, len(args))
		for _, arg := range args {
			inArgs = append(inArgs, arg.Interface())
		}
		// get remote session
		pooledObj, err := c.sessionPool.BorrowObject(c.ctx)
		// fmt.Println("borrow obj from pooled, ", pooledObj)
		if err != nil {
			log.Printf("Error:invoke remote method: %s get session form poll err:%v", rpcName, err)
			return zeroValueOut(fPtr)
		}
		defer func() {
			if pooledObj != nil {
				err := c.sessionPool.ReturnObject(c.ctx, pooledObj)
				// fmt.Println("return obj to pooled, ", pooledObj)
				if err != nil {
					log.Printf("Error PooledClient put session err: %v", err)
				}
			}
		}()
		cliSession := pooledObj.(*MyPooledObj).session

		// 编码数据 //reqRPC := RPCData{Name: rpcName, Args: inArgs}
		reqRPC := Request{Name: rpcName, Args: inArgs}
		b, err := encode(reqRPC)
		if err != nil {
			panic(err)
		}

		// 写出数据
		requestTimestamp := time.Now()
		err = cliSession.Write(b)
		if err != nil {
			log.Println("call rpc write data error, ", err)
			return zeroValueOut(fPtr)
		}
		// 读响应数据
		respBytes, err := cliSession.Read()
		if err != nil {
			log.Println("call rpc read data error, ", err)
			return zeroValueOut(fPtr)
		}
		responseTimestamp := time.Now()
		log.Printf("entry rpc invocation:%v, invoke at:%s, response at:%s, spent:%d ms", reqRPC, requestTimestamp.Format(time.RFC3339), responseTimestamp.Format(time.RFC3339), (responseTimestamp.UnixNano()-requestTimestamp.UnixNano())/1000000)

		// 解码数据 //respRPC, err := decode(respBytes)
		respRPC, err := decodeRequest(respBytes)
		if err != nil {
			log.Println("client rpc decode error, ", err)
			return zeroValueOut(fPtr)
		}

		// 处理服务端返回的数据
		outArgs := make([]reflect.Value, 0, len(respRPC.Args))
		for i, arg := range respRPC.Args {
			// 必须进行nil转换
			if arg == nil {
				// 必须填充一个真正的类型，不能是nil
				outArgs = append(outArgs, reflect.Zero(fn.Type().Out(i)))
				continue
			}
			// 添加正常返回值
			outArgs = append(outArgs, reflect.ValueOf(arg))
		}
		return outArgs
	}

	// 为函数fPtr赋值
	fn.Set(reflect.MakeFunc(fn.Type(), f))
}

// zeroValueOut 返回值列表填充空值
func zeroValueOut(fooPtr interface{}) []reflect.Value {
	fn := reflect.ValueOf(fooPtr).Elem()
	outNum := fn.Type().NumOut()
	zeroOut := make([]reflect.Value, 0, outNum)
	for i := 0; i < outNum; i++ {
		zeroOut = append(zeroOut, reflect.Zero(fn.Type().Out(i)))
	}
	return zeroOut
}
